package com.zlcx.tz.live.service.task;

import com.zlcx.tz.live.common.annotation.AutoLog;
import com.zlcx.tz.live.constant.RedisKeyConst;
import com.zlcx.tz.live.domain.TimeLearnRoomDO;
import com.zlcx.tz.live.domain.TimeLearnRoomUsersDO;
import com.zlcx.tz.live.exception.SendExceptionUtil;
import com.zlcx.tz.live.service.TimeLearnRoomDialogStore;
import com.zlcx.tz.live.service.TimeLearnRoomService;
import com.zlcx.tz.live.utils.SimpleLockUtil;
import com.zlcx.tz.live.vo.DialogVO;
import com.zlcx.tz.live.vo.TimeLearnRoomSeatsVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.zlcx.tz.live.domain.TimeLearnRoomUsersDO.FIELD_CREATE_TIME;
import static com.zlcx.tz.live.domain.TimeLearnRoomUsersDO.TimeLearnRoomUserState;
import static com.zlcx.tz.live.service.impl.RedisTimeLearnRoomDialogStoreImpl.HEART_INTERVAL;

/**
 * @author anjl
 * @version 1.0
 * @date Created in 2019/4/23 17:21
 */
@Service
@Slf4j
@EnableScheduling
public class TimeLearnRoomTaskService {

    /**
     * 巡检所有计时自习室用户以及会话状态，将没有心跳的观众踢出房间
     */
    @AutoLog
    @Scheduled(cron = "0 0/1 * * * ?")
    public void checkTimeLearnRoomUser() {

        if (debug) {
            log.warn("本地环境不启动定时");
            return;
        }

        if (!checkTimeLearnRoomLockKey()) {
            return;
        }

        try {
            log.info("计时自习室巡检");
            List<TimeLearnRoomDO> timeLearnRooms = timeLearnRoomService.allRooms();

            //针对每个房间，分开进行巡检
            for (TimeLearnRoomDO learnRoom : timeLearnRooms) {
                int a = 0, b = 0;
                boolean flag = true;
                Pageable pageable = new PageRequest(0, 300, Sort.Direction.ASC, FIELD_CREATE_TIME);
                Page<TimeLearnRoomUsersDO> page = null;

                //房间内用户分页巡检
                while (flag || page.hasNext()) {

                    if (Objects.nonNull(page)) {
                        a += page.getContent().size();
                    }

                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    flag = false;
                    page = timeLearnRoomService.findUsers(learnRoom.getId(), pageable);
                    if (page.getContent().isEmpty()) {
                        break;
                    }

                    //检查所有的观众
                    List<TimeLearnRoomUsersDO> audienceUsers = page
                            .getContent()
                            .stream()
                            .filter(r -> r.getState().equals(TimeLearnRoomUserState.FREE_TIME.name()))
                            .collect(Collectors.toList());
                    List<DialogVO> dialogs = timeLearnRoomDialogStore.all(learnRoom.getId());
                    for (TimeLearnRoomUsersDO room : audienceUsers) {
//                        log.debug("计时自习室巡检 room = {}", room);
                        timeLearnRoomExecutor.execute(() -> examAudience(dialogs, room));
                    }

                    //检查座位上学习的人，给所有正在学习没有心跳的用户发送心跳的，防止因为退出app，导致退出房间会话
                    List<TimeLearnRoomUsersDO> learningUsers = page.getContent()
                            .stream()
                            .filter(r -> r.getState().equals(TimeLearnRoomUserState.LEARNING_TIME.name()))
                            .collect(Collectors.toList());
                    learningUsers.forEach(user -> timeLearnRoomExecutor.submit(() -> makeUpHeart(dialogs, user)));
                    b += learningUsers.size();
                }
                log.info("计时自习室巡检, learnRoom = {} learnRoomName = {} 共检查信息 {} 条 自动心跳 {} 条", learnRoom.getId(), learnRoom.getRoomName(), a, b);
            }

        } finally {
            releaseTimeLearnRoomLockKey();
        }
    }


    /**
     * 补偿心跳，针对学习中的用户
     */
    private void makeUpHeart(List<DialogVO> dialogs, TimeLearnRoomUsersDO user) {
        try {
            Date now = new Date();
            DialogVO dialog = dialogs.stream().filter(d -> d.getUserId().equals(user.getUserId())).findFirst().orElse(null);
            //计时学习用户如果客户端已经进行了心跳，不需要再进行自动心跳了
            if (Objects.nonNull(dialog) && (now.getTime() - dialog.getTime().getTime()) / 1000 < HEART_INTERVAL * 3) {
                return;
            }
            timeLearnRoomService.heart(user.getRoomId(), user.getUserId());
        } catch (Exception e) {
            log.error("", e);
        }
    }

    /**
     * 检查观众自习室会话
     */
    private void examAudience(List<DialogVO> dialogs, TimeLearnRoomUsersDO room) {
        List<Long> dialogUserIds = dialogs.stream()
                .map(DialogVO::getUserId)
                .collect(Collectors.toList());

        //观众心跳消失，造成会话中没有用户信息，直接踢出房间
        if (!dialogUserIds.contains(room.getUserId())) {
            log.info("计时自习室巡检，查询到用户已经长时间没有心跳，不在房间中，踢出 room = {}", room);
            try {
                timeLearnRoomService.quitRoom(room.getRoomId(), room.getUserId());
            } catch (Exception e) {
                log.warn("计时自习室巡检-关闭房间出错 {}", e.getMessage());
            }
        }

        DialogVO dialog = dialogs.stream().filter(d -> d.getUserId().equals(room.getUserId())).findFirst().orElse(null);
        if (Objects.isNull(dialog)) {
            return;
        }

        //观众心跳已经超时，直接踢出房间
        Date now = new Date(), date = dialog.getTime();
        if ((now.getTime() - date.getTime()) / 1000 > (HEART_INTERVAL * 3)) {
            log.info("计时自习室巡检-用户心跳超时，自动退出房间 roomId = {} userId = {}", room.getRoomId(), room.getUserId());
            try {
                timeLearnRoomService.quitRoom(room.getRoomId(), room.getUserId());
            } catch (Exception e) {
                log.warn("计时自习室巡检-关闭房间出错 {}", e.getMessage());
            }
        }
    }

    private void releaseTimeLearnRoomLockKey() {
        String lockKey = RedisKeyConst.getCheckTimeLearnRoomLockKey();
        try {
            simpleLockUtil.releaseLock(lockKey, this.getClass().getName());
        } catch (Exception e) {
            log.error("releaseBackupCloseRoomLockKey error", e);
        }
    }

    private boolean checkTimeLearnRoomLockKey() {
        String lockKey = RedisKeyConst.getCheckTimeLearnRoomLockKey();

        try {
            return simpleLockUtil.acquireLock(lockKey, this.getClass().getName(), 30000);
        } catch (Exception e) {
            log.error("", e);
        }
        return false;
    }


    /**
     * 巡检所有计时自习室用户，将学习时长到期的用户结束计时并踢出房间
     */
    @AutoLog
    @Scheduled(cron = "0/10 * *  * * ?")
    public void kickingTimeLearnRoomUser() {

        if (debug) {
            log.warn("本地环境不启动定时");
            return;
        }
        if (!kickingTimeLearnRoomLockKey()) {
            return;
        }
        try {
            List<TimeLearnRoomSeatsVO> seats = timeLearnRoomService.findExpireUsers();
            seats.forEach(seat -> {
                try {
                    timeLearnRoomService.sitUpAndQuit(seat.getRoomId(), seat.getUserId());
                } catch (Exception e) {
                    log.error("定时踢出到时长的计时自习室用户出错 seats = {}", seats);
                    sendExceptionUtil.sendException("定时踢出到时长的计时自习室用户出错 userId = " + seat.getUserId() + "seatId = " + seat.getId(), e);
                    log.error("", e);
                }
            });
        } finally {
            releaseKickingTimeLearnRoomLockKey();
        }

    }

    private void releaseKickingTimeLearnRoomLockKey() {
        String lockKey = RedisKeyConst.getKickingTimeLearnRoomLockKey();
        try {
            simpleLockUtil.releaseLock(lockKey, this.getClass().getName());
        } catch (Exception e) {
            log.error("releaseBackupCloseRoomLockKey error", e);
        }
    }

    private boolean kickingTimeLearnRoomLockKey() {
        String lockKey = RedisKeyConst.getKickingTimeLearnRoomLockKey();

        try {
            return simpleLockUtil.acquireLock(lockKey, this.getClass().getName(), 30000);
        } catch (Exception e) {
            log.error("", e);
        }
        return false;
    }


    @Value("${eureka.debug:false}")
    private Boolean debug;
    @Autowired
    private SimpleLockUtil simpleLockUtil;
    @Autowired
    private TimeLearnRoomService timeLearnRoomService;
    @Autowired
    private TimeLearnRoomDialogStore timeLearnRoomDialogStore;
    @Resource(name = "timeLearnRoomExecutor")
    AsyncTaskExecutor timeLearnRoomExecutor;
    @Autowired
    private SendExceptionUtil sendExceptionUtil;

}
